﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Net.Mail;
using System.Net.Security;
using System.Net.Sockets;
using System.Text;

namespace Antix.Mail.Smtp.Impostor
{
    public class ForwardMessageStorage : IMessageStorage
    {
        readonly ForwardMessageStorageConfiguration _configuration;

        public ForwardMessageStorage(
            ForwardMessageStorageConfiguration configuration)
        {
            _configuration = configuration;
        }

        public int Count => 0;

        public IEnumerator<MessageInfo> GetEnumerator()
        {
            yield break;
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public event NotifyCollectionChangedEventHandler CollectionChanged;

        public void Dispose()
        {
        }

        public bool Contains(string id)
        {
            throw new NotImplementedException();
        }

        public void Store(Message message)
        {
            using (var client = new TcpClient())
            {
                client.Connect(_configuration.Server, _configuration.Port);

                if (!string.IsNullOrWhiteSpace(_configuration.PrefixSubject))
                {
                    message.Data = message.Data;
                }

                using (var stream = client.GetStream())
                {
                    var read = GetRead(stream);
                    var write = GetWrite(stream);

                    read(220);

                    write($"EHLO {_configuration.Server}");
                    read(250);

                    if (_configuration.UseTls)
                    {
                        write("STARTTLS");
                        read(220);

                        using (var sslStream = new SslStream(stream))
                        {
                            sslStream.AuthenticateAsClient(_configuration.Server);
                            var readSsl = GetRead(sslStream);
                            var writeSsl = GetWrite(sslStream);

                            SendMail(writeSsl, readSsl, message.Data);
                        }
                    }
                    else
                    {
                        SendMail(write, read, message.Data);
                    }
                }
            }
        }

        void SendMail(Action<string> write, Action<int> read, string data)
        {
            if (!string.IsNullOrWhiteSpace(_configuration.User))
            {
                write("AUTH LOGIN");
                read(334);

                write(Convert.ToBase64String(Encoding.UTF8.GetBytes(_configuration.User)));
                read(334);
                write(Convert.ToBase64String(Encoding.UTF8.GetBytes(_configuration.Password)));
                read(235);
            }

            write($"MAIL FROM:<{_configuration.From}>");
            read(250);

            write($"RCPT TO:<{_configuration.ForwardTo}>");
            read(250);

            write("DATA");
            read(354);

            write($"{data}\r\n.");
            read(250);
        }

        static Action<int> GetRead(Stream stream)
        {
            var reader = new StreamReader(stream);

            return expected =>
            {
                bool end;
                var errors = new List<string>();
                do
                {
                    var response = reader.ReadLine();
                    if (response == null)
                        throw new SmtpException("Null Response");

                    SmtpStatusCode code;
                    Enum.TryParse(response.Substring(0, 3), out code);
                    var message = response.Substring(3).TrimStart();

                    if ((int)code >= 400)
                    {
                        Log.Error(response);
                        errors.Add(message);

                        if (reader.Peek() == -1)
                            throw new SmtpException(
                                code,
                                string.Join(" - ", errors));
                    }
                    else
                        Log.Information(response);

                    end = reader.Peek() == -1;
                } while (!end);
            };
        }

        static Action<string> GetWrite(Stream stream)
        {
            return request =>
            {
                if (request != null)
                    Log.Information(request);

                var bytes = Encoding.UTF8.GetBytes(request + "\r\n");
                stream.Write(bytes, 0, bytes.Length);
                stream.Flush();
            };
        }

        public Message Retrieve(string id)
        {
            throw new NotImplementedException();
        }

        public MessageInfo RetrieveInfo(string id)
        {
            throw new NotImplementedException();
        }

        public void Delete(string id)
        {
            throw new NotImplementedException();
        }

        public void DeleteAll()
        {
            throw new NotImplementedException();
        }
    }
}